﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Lucene.Net.Store;
using Lucene.Net.Index;
using Lucene.Net.Analysis.Standard;
using MyNote.Data.Models;
using Lucene.Net.Documents;
using System.Diagnostics;
using System.IO;
using Lucene.Net.Search;
using Lucene.Net.QueryParsers;
using System.Threading;

namespace MyNote.FullText
{
    public class IndexWorkBlock
    {
        public NoteModelContainer Context { get; set; }

        public Guid EntityId { get; set; }
    }

    public class IndexWorker
    {
        private object _indexLock = new object();
        private Lucene.Net.Store.Directory _indexDirectory;
        // private string _indexDirectory;
        private Queue<IndexWorkBlock> _blocks;
        private StandardAnalyzer _analyzer;        
        private AutoResetEvent _event;
        private bool _stopThreads;

        public IndexWorker(Lucene.Net.Store.Directory directory)
        {
            _indexDirectory = directory;
            _analyzer = new StandardAnalyzer();
            _event = new AutoResetEvent(false);
            _blocks = new Queue<IndexWorkBlock>();

            if (!IndexReader.IndexExists(directory))
            {
                new IndexWriter(directory, _analyzer, true).Close();
            }
        }

        public void StopIndexThreads()
        {
            _stopThreads = true;
            _event.Set();
        }

        public void QueueWork(IndexWorkBlock block)
        {
            if (block == null)
                throw new ArgumentNullException("block");
            if (_stopThreads)
                throw new InvalidOperationException("索引线程已经终止了!");

            lock (_blocks)
            {
                _blocks.Enqueue(block);
            }

            _event.Set();
        }

        public void CreateIndexThread(object obj)
        {
            using (var context = (NoteModelContainer)obj)
            {
                var idxWriter = new IndexWriter(_indexDirectory, _analyzer, false);
                try
                {
                    foreach (var note in context.NoteSet)
                    {
                        if (_stopThreads)
                            break;

                        try
                        {
                            lock (_indexLock)
                            {
                                idxWriter.AddDocument(CreateDoc(note));
                            }
                        }
                        catch (Exception ex)
                        {
                            Trace.TraceError(string.Format("[CreateIndexThread] 发生异常: {0}", ex));
                        }

                    }

                    idxWriter.Optimize();
                }
                catch (Exception ex)
                {
                    Trace.TraceError(string.Format("[CreateIndexThread] IndexWriter.Optimize发生异常: {0}", ex));
                }
                finally
                {
                    idxWriter.Close();
                }
            }
        }

        private Document CreateDoc(Note note)
        {
            var doc = new Document();
            doc.Add(new Field("note_id", note.Id.ToString(),
                Field.Store.YES, Field.Index.UN_TOKENIZED));

            if (!string.IsNullOrEmpty(note.Title))
            {
                doc.Add(new Field("raw_title", note.Title, Field.Store.YES, Field.Index.UN_TOKENIZED));
                doc.Add(new Field("title", note.Title, Field.Store.YES, Field.Index.TOKENIZED));
            }

            if ( !string.IsNullOrEmpty(note.Tag) )
                doc.Add(new Field("tag", note.Tag, Field.Store.YES, Field.Index.TOKENIZED));

            if ( string.IsNullOrEmpty(note.Description) )
                doc.Add(new Field("description", note.Description, Field.Store.YES, Field.Index.TOKENIZED));

            return doc;
        }

        private Searcher _searcher = null;
        public void UpdateIndexThread(object state)
        {
            while (!_stopThreads)
            {
                if (!_event.WaitOne(-1))
                    break;

                IndexWorkBlock block = null;
                lock (_blocks)
                {
                    if (_blocks.Count == 0)
                        break;

                    block = _blocks.Dequeue();
                }

                var note = block.Context.NoteSet.Single(n => n.Id == block.EntityId);
                lock (_indexLock)
                {
                    var modifier = new IndexModifier(_indexDirectory, _analyzer, false);
                    try
                    {
                        if (_searcher != null)
                        {
                            _searcher.Close();
                        }
                        _searcher = null;
                        modifier.DeleteDocuments(new Term("note_id", note.Id.ToString()));

                        modifier.AddDocument(CreateDoc(note));
                        modifier.Flush();
                    }
                    catch (Exception e)
                    {
                        Trace.TraceError(string.Format("[UpdateIndexThread] 更新笔记{0} - {1}的全文索引时发生异常:{2}", note.Title, note.Id, e));
                    }
                    finally
                    {
                        modifier.Close();
                    }
                }
            }
        }

        public Note[] DoFullTextSearch(string terms)
        {
            var hits = DoFullTextSearchImpl(terms);
            var notes = new List<Note>();

            for (int i = 0; i < hits.Length(); ++i)
            {
                var hit = hits.Doc(i);
                var rawid = hit.Get("note_id");
                var title = hit.Get("raw_title");
                var id = new Guid(rawid);

                if (!notes.Any(n => n.Id.CompareTo(id) == 0))
                {
                    notes.Add(new Note() { Id = id, Title = title });
                }
            }

            return notes.ToArray();
        }

        private Hits DoFullTextSearchImpl(string terms)
        {
            if (string.IsNullOrEmpty(terms))
                throw new ArgumentNullException("terms");

            var parser = new QueryParser("description", _analyzer);
            var query = parser.Parse(string.Format("({0} OR (title:{0}) OR (tag:{0}))", terms));

            lock (_indexLock)
            {
                if (_searcher == null)
                    _searcher = new IndexSearcher(_indexDirectory);

                var hits = _searcher.Search(query);
                return hits;
            }
        }
    }
}